

import numpy as np
import new_helpers as helpers

#
# This is a simple implementation I have looked up in the wiki
# THIS ONE is used by Turnable class currently
#
def make_rotation_matrix( nn, a ):
    """ return rotation matrix for angle a around nn """
    R = np.zeros( (3,3) )
    R[0,0] = nn[0]*nn[0] * (1-np.cos(a)) + np.cos(a)
    R[1,0] = nn[0]*nn[1] * (1-np.cos(a)) + nn[2]*np.sin(a)
    R[2,0] = nn[0]*nn[2] * (1-np.cos(a)) - nn[1]*np.sin(a)
    
    R[0,1] = nn[0]*nn[1] * (1-np.cos(a)) - nn[2]*np.sin(a)
    R[1,1] = nn[1]*nn[1] * (1-np.cos(a)) + np.cos(a)
    R[2,1] = nn[1]*nn[2] * (1-np.cos(a)) + nn[0]*np.sin(a)
    
    R[0,2] = nn[0]*nn[2] * (1-np.cos(a)) + nn[1]*np.sin(a)
    R[1,2] = nn[1]*nn[2] * (1-np.cos(a)) - nn[0]*np.sin(a)
    R[2,2] = nn[2]*nn[2] * (1-np.cos(a)) + np.cos(a)
        
    return R

#
# This is a smarter implementation I have looked up on the web somewhere
# I have timed it and it seems not to be faster then the upper one
# However the returned rotation matrices of both differ by a sign
# in two off axis elements... not sure what this means :-|
#
def make_axis_rotation_matrix(direction, angle):
    """
        Create a rotation matrix corresponding to the rotation around a general
        axis by a specified angle.
        R = dd^T + cos(a) (I - dd^T) + sin(a) skew(d)
        Parameters:
        angle : float a
        direction : array d
    """
    d = np.array(direction, dtype=np.float64)
    d /= np.linalg.norm(d)
    eye = np.eye(3, dtype=np.float64)
    ddt = np.outer(d, d)
    skew = np.array([[    0,  d[2],  -d[1]],
                     [-d[2],     0,  d[0]],
                     [d[1], -d[0],    0]], dtype=np.float64)
    mtx = ddt + np.cos(angle) * (eye - ddt) + np.sin(angle) * skew
    return mtx


class Turnable( object ):
    """ *Virtual* class for inheritance of turn() method only
    
        turn( axis, angle) - turn any member of class, which is
        contained in list-like self._turnables.
    
        sub-class constructor should fill self._turnables
    """
    
    def turn(self, axis, angle):
        """ turn all members in self.turnables around *axis* by angle
            
            *axis* : can be any 3-element np.array or np.array - constructable like e.g. a list 
            *angle* : in radians
        """
        
        axis = np.array(axis)
        
        if helpers.length(axis) != 1.:
            axis /= helpers.length(axis)
        
        R = make_rotation_matrix( axis, angle )
        
        for turnable in self._turnables:
            if hasattr(self, turnable):
                setattr( self, turnable,  np.dot(R, getattr( self, turnable)) )
    
    # standard __repr__
    def __repr__( self ):
        return "%s(%r)" % (self.__class__, self.__dict__)

